﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

//TODO: ColorBlobExtractor, speedup

namespace LowLevelGraphics
{
    delegate bool IsInToleranceFloat(Color color1, Color color2, float fTolerance);
    delegate bool IsInToleranceColor(Color color1, Color color2, int r, int g, int b);

    /// <summary>
    /// Color Blob Extractor
    /// </summary>
    public class ColorBlobExtractor: IExtractor
    {
        private static bool m_bEnlargeRectangles = false;
        private Color m_ColorBlobRectangle = Color.Blue;
        private int m_nHeight = -1;
        private int m_nWidth = -1;

        private bool m_bJoinRectangles = true;

        /// <summary>
        /// Initializes a new instance of the <see cref="ColorBlobExtractor"/> class.
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        public ColorBlobExtractor(Bitmap bitmap)
            :this(bitmap, new Size(5,5))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ColorBlobExtractor"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_szStep">The _SZ step.</param>
        public ColorBlobExtractor(UnsafeBitmap _bitmap, Size _szStep)
        {
            bitmap = _bitmap;
            m_szStep = _szStep;
            this.m_nHeight = _bitmap.Height;
            this.m_nWidth = _bitmap.Width;
        }

        UnsafeBitmap bitmap = null;

        public bool EnlargeRectangles
        {
            get { return m_bEnlargeRectangles; }
            set { m_bEnlargeRectangles = value; }
        }

        /// <summary>
        /// Gets or sets the bitmap.
        /// </summary>
        /// <value>The bitmap.</value>
        public UnsafeBitmap Bitmap
        {
            get { return bitmap; }
            set
            {
                bitmap = value;
                m_nHeight = value.Height;
                m_nWidth = value.Width;
            }
        }

        List<ColorRect> m_aColorRect = new List<ColorRect>();
        List<Color> aColor = new List<Color>();
        List<int> aInt = new List<int>();

        private Size m_szStep = new Size();
        //private int m_nErrorTolerance = 0; ////how many pixels might be wrong...

        /// <summary>
        /// Find blobs
        /// </summary>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public List<ColorRect> ExtractBlobs(int r, int g, int b)
        {
            aColor = new List<Color>() { bitmap.GetPixel(0, 0) };
            List<KeyValuePair<int, int>> aCoord = new List<KeyValuePair<int, int>>();

            int nCount = 1;
            List<Rectangle> aRect = new List<Rectangle>();

            int y = 0;
            int x = 0;

            List<Point> aPoint = new List<Point>();


            bool bStartFound = false;
            int nInvalidRectangleCount = 0;
            Color colorCurrent = Color.Transparent;
            Color colorPrev = Color.Transparent;
            Rectangle rect = Rectangle.Empty;

            for (y = 0; y < bitmap.Height; y+=m_szStep.Height)
            {
                for (x = 0; x < bitmap.Width; x+=m_szStep.Width)
                {
                    rect = BlobRectangle(bitmap, new Point(x, y), r,g,b);
                    if (rect != Rectangle.Empty && rect.Width>0 && rect.Height>0)
                    {
                        m_aColorRect.Add(new ColorRect(ColorFunctions.Invert(bitmap.GetPixel(x,y)), rect));
                        x = rect.Right;
                    }
                    else
                    {
                        nInvalidRectangleCount++;
                    }
                }
            }

            if (m_bJoinRectangles)
            {
//                JoinRectangles(r,g,b);
            }

            object o = ColorRectangleDictionary;
            bitmap.UnlockImage();
            return m_aColorRect;
        }

        /*private void JoinRectangles(int r, int g, int b)
        {
            List<ColorRect> aLocalColorRect = new List<ColorRect>();

            for (int j = 0; j < m_aColorRect.Count-1; j++)
            {
                for (int i = 0; i < m_aColorRect.Count-1; i++)
                {
                    Color colorPrev = m_aColorRect[i].Color;
                    Color colorCurrent = m_aColorRect[j].Color;

                    if (ColorFunctions.IsInColorTolerance(colorPrev, colorCurrent, r, g, b))
                    {
                        bool bIntersectsWithInclusive = m_aColorRect[i].Rectangle.IntersectsWithInclusive(m_aColorRect[j].Rectangle);
                        if (bIntersectsWithInclusive)
                        {
                            Rectangle rect = Rectangle.Union(m_aColorRect[i].Rectangle, m_aColorRect[j].Rectangle);
                            Color color = Color.FromArgb((m_aColorRect[i].Color.ToArgb() + m_aColorRect[j].Color.ToArgb()) / 2);
                            aLocalColorRect.Add(new ColorRect(color, rect));
                            m_aColorRect.Remove(m_aColorRect[i]);
                            m_aColorRect.Remove(m_aColorRect[j]);
                            continue;
                        }
                    }
                }
            }
            m_aColorRect.Clear();
            m_aColorRect.AddRange(aLocalColorRect);
        }*/


        /// <summary>
        /// Gets the rectangle count.
        /// </summary>
        /// <value>The rectangle count.</value>
        public int RectangleCount
        {
            get { return m_aColorRect.Count; }
        }

        /// <summary>
        /// Gets the color rectangle dictionary.
        /// </summary>
        /// <value>The color rectangle dictionary.</value>
        public Dictionary<Color, List<Rectangle>> ColorRectangleDictionary
        {
            get 
            {
                Dictionary<Color, List<Rectangle>> aDict = new Dictionary<Color, List<Rectangle>>();

                foreach(ColorRect colorRect in m_aColorRect)
                {
                    if (!aDict.ContainsKey(colorRect.Color))
                    {
                        aDict.Add(colorRect.Color, new List<Rectangle>() { colorRect.Rectangle });
                    }
                    else
                    {
                        aDict[colorRect.Color].Add(colorRect.Rectangle);
                    }
                }
                return aDict;
            }
        }
        //private void JoinRectangles()
        //{
        //    List<ColorRect> aLocalColorRect = new List<ColorRect>();

        //    for (int i = 1; i < aColorRect.Count; i++)
        //    {
        //        Color colorPrev = aColorRect[i - 1].Color;
        //        Color colorCurrent = aColorRect[i].Color;
        //        if (ImageFilters.IsInColorTolerance(colorPrev, colorCurrent, 3, 3, 3))
        //        {
        //            bool b = aColorRect[i].Rectangle.IntersectsWithInclusive(aColorRect[i - 1].Rectangle);
        //            if (b)
        //            {
        //                Rectangle rect = Rectangle.Union(aColorRect[i].Rectangle, aColorRect[i - 1].Rectangle);
        //                Color color = Color.FromArgb((aColorRect[i].Color.ToArgb() + aColorRect[i - 1].Color.ToArgb()) / 2);
        //                aLocalColorRect.Add(new ColorRect(color, rect));
        //            }
        //        }
        //    }
        //    aColorRect.Clear();
        //    aColorRect.AddRange(aLocalColorRect);
        //}

        /// <summary>
        /// BLOBs the rectangle.
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        /// <param name="ptStart">The pt start.</param>
        /// <param name="fValue">The f value.</param>
        /// <returns></returns>
        public static Rectangle BlobRectangle(UnsafeBitmap bitmap, Point ptStart, float fValue)
        {
            return BlobRectangle(bitmap, PROCESSING_MODE.Color, ptStart, fValue);
        }

        /// <summary>
        /// BLOBs the rectangle.
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        /// <param name="ptStart">The pt start.</param>
        /// <param name="r">The r.</param>
        /// <param name="g">The g.</param>
        /// <param name="b">The b.</param>
        /// <returns></returns>
        public static Rectangle BlobRectangle(UnsafeBitmap bitmap, Point ptStart, int r, int g, int b)
        {
            return BlobRectangle(bitmap, PROCESSING_MODE.Color, ptStart, Color.FromArgb(r,g,b));
        }

        /// <summary>
        /// BLOBs the rectangle.
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        /// <param name="_mode">The _mode.</param>
        /// <param name="ptStart">The pt start.</param>
        /// <param name="_oValue">The _o value.</param>
        /// <returns></returns>
        public static Rectangle BlobRectangle(UnsafeBitmap bitmap,PROCESSING_MODE _mode, Point ptStart, object _oValue)
        {
            Delegate delegateInTolerance = null;
            switch (_mode)
            {
                case PROCESSING_MODE.Brightness:
                    delegateInTolerance = new IsInToleranceFloat(ColorFunctions.IsBrightNessTolerance);
                    break;

                case PROCESSING_MODE.Color:
                    delegateInTolerance = new IsInToleranceColor(ColorFunctions.IsInColorTolerance);
                    break;

                case PROCESSING_MODE.Hue:
                    delegateInTolerance = new IsInToleranceFloat(ColorFunctions.IsInHueTolerance);
                    break;

                case PROCESSING_MODE.Luminance:
                    delegateInTolerance = new IsInToleranceFloat(ColorFunctions.IsInLuminanceTolerance);
                    break;

                case PROCESSING_MODE.Saturation:
                    delegateInTolerance = new IsInToleranceFloat(ColorFunctions.IsInSaturationTolerance);
                    break;

            }
            Rectangle rectResult = Rectangle.Empty;

            Color color = Color.Transparent;
            float fValue = 0.0f;
            if (_oValue is Color)
            {
                color = (Color)_oValue;
            }
            else if (_oValue is float)
            {
                fValue = (float)fValue;
            }

            int nLength = 0;

            ColorBlobExtractorDirection direction = new ColorBlobExtractorDirection();

            List<Point> aPoint = new List<Point>();
            int nX = ptStart.X;
            int nY = ptStart.Y;

            int nCount = 0;
            List<Color> aColor = new List<Color>();

            if (BoundLimitsReached(bitmap, nX, nY))
            {
                return rectResult;
            }
            aColor.Add(bitmap.GetPixel(ptStart.X, ptStart.Y));
            nCount++;

            //This will do the trick.
            bool bResult = false;
            for (nLength = 1; nLength < 200; nLength++)
            {
                for (int nDir = 0; nDir < 2; nDir++)
                {
                    for (int nWalk = 1; nWalk <= nLength; nWalk++)
                    {
                        nX = nX + 1 * direction.CurrentDirection.Width;
                        nY = nY + 1 * direction.CurrentDirection.Height;
                        if (BoundLimitsReached(bitmap, nX, nY))
                        {
                            break;
                        }
                        aPoint.Add(new Point(nX, nY));
                        aColor.Add(bitmap.GetPixel(nX, nY));
                        Color colorCurrent = aColor[nCount];
                        Color colorPrev = aColor[nCount - 1];

                        if (_mode != PROCESSING_MODE.Color)
                        {
                            bResult = (bool)delegateInTolerance.DynamicInvoke(colorCurrent, colorPrev, fValue);
                        }
                        else
                        {
                            bResult = ColorFunctions.IsInColorTolerance(colorCurrent, colorPrev, color.R, color.G, color.B);
                        }
                        if (!bResult)
                        {
                            goto calcRect;
                        }
                        nCount++;
                    }
                    direction.CycleDir();
                }
            }


        calcRect:
            aPoint.Sort(new PointSort(PointSort.Mode.X));
            int nX1 = aPoint[0].X;
            int nX2 = aPoint[aPoint.Count - 1].X;

            aPoint.Sort(new PointSort(PointSort.Mode.Y));
            int nY1 = aPoint[0].Y;
            int nY2 = aPoint[aPoint.Count - 1].Y;

            rectResult = RectangleHelpers.CalcRectangle(new Point(nX1, nY1), new Point(nX2, nY2));

            if (m_bEnlargeRectangles)
            {
                EnlargeRectangle(bitmap, color.R, color.G, color.B, ref rectResult, ref nX, ref nY, ref nCount, aColor);
            }
            return rectResult;
        }

        /// <summary>
        /// Enlarges the rectangle.
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        /// <param name="r">The r.</param>
        /// <param name="g">The g.</param>
        /// <param name="b">The b.</param>
        /// <param name="rectResult">The rect result.</param>
        /// <param name="nX">The n X.</param>
        /// <param name="nY">The n Y.</param>
        /// <param name="nCount">The n count.</param>
        /// <param name="aColor">A color.</param>
        private static void EnlargeRectangle(UnsafeBitmap bitmap, int r, int g, int b, ref Rectangle rectResult, ref int nX, ref int nY, ref int nCount, List<Color> aColor)
        {
            aColor.Clear();
            nCount = 1;

            nX = rectResult.Left;
            nY = rectResult.Top;
            aColor.Add(bitmap.GetPixel(nX, nY));
            for (int i = rectResult.Left; i < rectResult.Right; i++)
            {
                nX = i;
                aColor.Add(bitmap.GetPixel(nX, nY));
                Color colorCurrent = aColor[nCount];
                Color colorPrev = aColor[nCount - 1];
                if (!ColorFunctions.IsInColorTolerance(colorPrev, colorCurrent, r, g, b))
                {
                    break;
                }
                nCount++;
            }

            aColor.Clear();
            nCount = 1;
            nX = rectResult.Left;
            nY = rectResult.Bottom;
            aColor.Add(bitmap.GetPixel(nX, nY));
            for (int i = rectResult.Left; i < rectResult.Right; i++)
            {
                nX = i;
                aColor.Add(bitmap.GetPixel(nX, nY));
                Color colorCurrent = aColor[nCount];
                Color colorPrev = aColor[nCount - 1];
                if (!ColorFunctions.IsInColorTolerance(colorPrev, colorCurrent, r, g, b))
                {
                    break;
                }
                nCount++;
            }

            aColor.Clear();
            nCount = 1;
            nX = rectResult.Left;
            nY = rectResult.Top;
            aColor.Add(bitmap.GetPixel(rectResult.Right, rectResult.Top));
            for (int i = rectResult.Top; i < rectResult.Bottom; i++)
            {
                nY = i;
                aColor.Add(bitmap.GetPixel(nX, nY));
                Color colorCurrent = aColor[nCount];
                Color colorPrev = aColor[nCount - 1];
                if (!ColorFunctions.IsInColorTolerance(colorPrev, colorCurrent, r, g, b))
                {
                    break;
                }
                nCount++;
            }

            aColor.Clear();
            nCount = 1;
            nX = rectResult.Right;
            nY = rectResult.Top;
            aColor.Add(bitmap.GetPixel(rectResult.Right, rectResult.Top));
            for (int i = rectResult.Top; i < rectResult.Bottom; i++)
            {
                nY = i;
                aColor.Add(bitmap.GetPixel(nX, nY));
                Color colorCurrent = aColor[nCount];
                Color colorPrev = aColor[nCount - 1];
                if (!ColorFunctions.IsInColorTolerance(colorPrev, colorCurrent, r, g, b))
                {
                    break;
                }
                nCount++;
            }
        }

        /// <summary>
        /// Bounds the limits reached.
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        /// <param name="nX">The n X.</param>
        /// <param name="nY">The n Y.</param>
        /// <returns></returns>
        public static bool BoundLimitsReached(IBitmap bitmap, int nX, int nY)
        {
            return (nY > bitmap.Height - 1 || nY < 0 || nX > bitmap.Width - 1 || nX < 0);
        }
    }
}
